/**
 * $Id$
 * @author mcanovas
 * @date   Mar 6, 2012 4:52:29 PM
 *
 * Copyright (C) 2012 Scytl Secure Electronic Voting SA
 *
 * All rights reserved.
 *
 */
package org.sk.gsync.app.synch;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;

import org.apache.log4j.Logger;
import org.sk.gsync.app.mybatis.Folder;
import org.sk.gsync.app.utils.Tree;
import org.sk.gsync.app.utils.file.DirWatcher;
import org.springframework.beans.factory.annotation.Autowired;

import com.google.gdata.data.Link;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.util.ServiceException;

public class SynchronizationManager {

    private static Logger LOGGER = Logger
        .getLogger(SynchronizationManager.class);

    @Autowired
    private GoogleDocsHelper googleDocsHelper;

    @Autowired
    private DirWatcher dirWatcher;

    private final ArrayList<DefaultSynchItem<DocumentListEntry>> pending =
        new ArrayList<DefaultSynchItem<DocumentListEntry>>();

    private final HashMap<String, DefaultSynchItem<DocumentListEntry>> gItems =
        new HashMap<String, DefaultSynchItem<DocumentListEntry>>();

    private final HashMap<String, DefaultSynchItem<File>> lItems =
        new HashMap<String, DefaultSynchItem<File>>();

    private final Tree<DefaultSynchItem<DocumentListEntry>> gTree =
        new Tree<DefaultSynchItem<DocumentListEntry>>(
            new DefaultSynchItem<DocumentListEntry>());

    private Tree<DefaultSynchItem<File>> lTree = null;

    public void mapGDocs() throws IOException, ServiceException {
        List<DocumentListEntry> documents =
            googleDocsHelper.getAllEntriesFromServer();
        List<DefaultSynchItem<DocumentListEntry>> synchItems =
            getSyncItems(documents);
        for (DefaultSynchItem<DocumentListEntry> folder : synchItems) {
            resolveLevel(folder);
            Integer level = folder.getLevel();
            if (level != null && level == 0) {
                addFolder(folder);
            } else if (folder.isPlaced()) {
                putInPlace(folder);
            }
            calculatePending();
        }
        LOGGER.debug(gTree.printTree(0));
    }

    public void mapLocal(final Folder folder) {
        readFolder(folder.getPath());
        LOGGER.debug(lTree.printTree(0));
    }

    public Tree<DefaultSynchItem<File>> getLocalTree() {
        return lTree;
    }

    public Tree<DefaultSynchItem<DocumentListEntry>> getRemoteTree() {
        return gTree;
    }

    public DefaultSynchItem<File> getLocalFileItem(final String key) {
        return lItems.get(key);
    }

    public void setLocalFileItem(final String key,
            final DefaultSynchItem<File> value) {
        lItems.put(key, value);
    }

    public void startLocalWatcher(final Folder folder) {
        Timer timer = new Timer();
        dirWatcher.setPath(folder.getPath());
        timer.schedule(dirWatcher, new Date(), 1000);
    }

    public void createOperations() throws IOException, ServiceException {
        mapGDocs();
        Collection<DefaultSynchItem<File>> lSuccessors =
            lTree.getSuccessors(lTree.getHead());
        Collection<DefaultSynchItem<DocumentListEntry>> gSuccessors =
            gTree.getSuccessors(gTree.getHead());
        for (DefaultSynchItem<File> localItem : lSuccessors) {
            for (DefaultSynchItem<DocumentListEntry> gdataItem : gSuccessors) {
                if (gdataItem.equals(localItem)) {
                    gdataItem.getEmbeddedObject().getQuotaBytesUsed();
                    localItem.getEmbeddedObject().length();
                }
            }
        }
    }

    private void readFolder(final String pathFolder) {
        DefaultSynchItem<File> head = new DefaultSynchItem<File>();
        File root = new File(pathFolder);
        head.setEmbeddedObject(root);
        head.setLevel(-1);
        head.setParent(null);
        head.setTitle(root.getName());
        head.setRevision(0);
        lTree = new Tree<DefaultSynchItem<File>>(head);
        lItems.put(pathFolder, head);
        File[] files = root.listFiles();
        for (File file : files) {
            DefaultSynchItem<File> item = new DefaultSynchItem<File>();
            item.setParent(null);
            item.setEmbeddedObject(file);
            item.setTitle(file.getName());
            item.setLevel(0);
            item.setRevision(0);
            lTree.addLeaf(item);
            lItems.put(file.getAbsolutePath(), item);
            if (file.isDirectory()) {
                readSubFolders(item);
            }
        }
    }

    private void readSubFolders(final DefaultSynchItem<File> folder) {
        String absolutePath = folder.getEmbeddedObject().getAbsolutePath();
        File[] files = new File(absolutePath).listFiles();
        for (File file : files) {
            DefaultSynchItem<File> item = new DefaultSynchItem<File>();
            item.setParent(folder);
            item.setEmbeddedObject(file);
            item.setTitle(file.getName());
            item.setLevel(folder.getLevel() + 1);
            item.setRevision(0);
            lTree.getTree(folder).addLeaf(item);
            lItems.put(file.getAbsolutePath(), item);
            if (file.isDirectory()) {
                readSubFolders(item);
            }
        }
    }

    private List<DefaultSynchItem<DocumentListEntry>> getSyncItems(
            final List<DocumentListEntry> docList) {
        ArrayList<DefaultSynchItem<DocumentListEntry>> arrayList =
            new ArrayList<DefaultSynchItem<DocumentListEntry>>();
        for (DocumentListEntry documentListEntry : docList) {
            // TODO XXX Try to solve why it doesn't have edit link. As I saw, it
            // would be because I am not the owner.
            if (documentListEntry.getEditLink() != null) {
                DefaultSynchItem<DocumentListEntry> item =
                    new DefaultSynchItem<DocumentListEntry>();
                item.setEmbeddedObject(documentListEntry);
                item.setTitle(documentListEntry.getTitle().getPlainText());
                arrayList.add(item);
            }
        }
        return arrayList;
    }

    private void resolveLevel(
            final DefaultSynchItem<DocumentListEntry> item) {
        DocumentListEntry documentListEntry = item.getEmbeddedObject();
        List<Link> parentLinks = documentListEntry.getParentLinks();
        if (parentLinks.isEmpty()) {
            item.setLevel(0);
        } else {
            DefaultSynchItem<DocumentListEntry> parent = null;
            Iterator<Link> iterator = parentLinks.iterator();
            while (parent == null && iterator.hasNext()) {
                Link next = iterator.next();
                String parentHref = next.getHref();
                parent = gItems.get(parentHref);
            }
            if (parent == null) {
                if (!pending.contains(item)) {
                    pending.add(item);
                }
            } else {
                item.setLevel(parent.getLevel() + 1);
                item.setParent(parent);
                if (pending.contains(item)) {
                    pending.remove(item);
                }
            }
        }
    }

    private void addFolder(final DefaultSynchItem<DocumentListEntry> item) {
        gTree.addLeaf(item);
        gItems.put(item.getEmbeddedObject().getEditLink().getHref(), item);
    }

    private void putInPlace(final DefaultSynchItem<DocumentListEntry> item) {
        @SuppressWarnings("unchecked")
        DefaultSynchItem<DocumentListEntry> parent = item.getParent();
        Tree<DefaultSynchItem<DocumentListEntry>> treeChild =
            gTree.getTree(parent);
        treeChild.addLeaf(item);
        gItems.put(item.getEmbeddedObject().getEditLink().getHref(), item);
    }

    private void calculatePending() {
        @SuppressWarnings("unchecked")
        DefaultSynchItem<DocumentListEntry>[] items =
            pending.toArray(new DefaultSynchItem[pending.size()]);
        for (DefaultSynchItem<DocumentListEntry> pend : items) {
            resolveLevel(pend);
            if (pend.isPlaced()) {
                putInPlace(pend);
            }
        }
    }
}
